/**
 * Copyright (C) 2008+ Spark Zheng
 *
 * @file	src/str.c
 * @brief
 *      A str module need to work as String class in CPP.
 *      a clone from glib may be is enough.
 *
 * @see		dlib
 * @module      dlib
 *
 * @history
 *      version 0.1.0	Spark init str module, add str_trim/pack/toupper
 *      version 0.1.2	Spark add endian handle of pack & unpack
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "global.h"
#include "str.h"

/**
 * _trim_right
 * @brief Chomp the right space charactors
 * @param str: str which need to be trimmed
 */
static inline char *_trim_right(char *str);

/**
 * _trim_left
 * @brief Chomp the left space charactors
 * @param str: str which need to be trimmed
 */
static inline char *_trim_left(char *str);

static inline char *_trim_right(char *str)
{
	int len = 0;
	char *end_str;

	if (str == NULL)
		return NULL;
	len = strlen(str);
	if (len == 0)
		return NULL;

	end_str = str + len - 1;
	while (isspace(*end_str) && end_str != str) {
		end_str--;
	}

	*(end_str + 1) = '\0';

	return str;
}

static inline char *_trim_left(char *str)
{
	int len = 0;
	char *str_bak;
	char *str_tmp;

	if (str == NULL)
		return NULL;
	len = strlen(str);
	if (len == 0)
		return NULL;

	str_bak = str;
	while (isspace(*str_bak) && (*str_bak) != '\0') {
		str_bak++;
	}

	str_tmp = str;
	if (str_bak != str_tmp) {
		while (*str_bak != '\0') {
			*(str_tmp++) = *(str_bak++);
		}
		*str_tmp = '\0';
	}

	return str;
}

inline char *str_trim(char *str)
{
	if (str == NULL)
		return NULL;
	_trim_right(str);
	_trim_left(str);
	return str;
}

inline char *str_ltrim(char *str)
{
	if (str == NULL)
		return NULL;
	return _trim_left(str); 
}

inline char *str_rtrim(char *str)
{
	if (str == NULL)
		return NULL;
	return _trim_right(str);
}

inline int str_pack(char *buf, char *fmt, ...)
{
	char *c;
	char *bp;

	uint64_t ll;
	uint32_t l;
	uint32_t d;
	uint16_t s;

	va_list args;

	va_start(args, fmt);
	bp = buf;
	for (c = fmt; *c != '\0'; c++) {
		switch (*c) {
		case 'L':
			ll = va_arg(args, uint64_t);
#ifdef _DLIB_BIG_ENDIAN
			l = *((uint32_t *) & ll);
			l = htonl(l);
			memmove(bp, (char *)&l, sizeof(uint32_t));
			bp += sizeof(uint32_t);
			l = *(uint32_t *)((uint8_t *) & ll + sizeof(uint32_t));
			l = htonl(l);
			memmove(bp, (char *)&l, sizeof(uint32_t));
			bp += sizeof(uint32_t);
#else
			l = *(uint32_t *)((uint8_t *) & ll + sizeof(uint32_t));
			l = htonl(l);
			memmove(bp, (char *)&l, sizeof(uint32_t));
			bp += sizeof(uint32_t);
			l = *((uint32_t *) & ll);
			l = htonl(l);
			memmove(bp, (char *)&l, sizeof(uint32_t));
			bp += sizeof(uint32_t);
#endif
			break;
		case 'l':
#if (_DLIB_WORDSIZE == 64)
			ll = va_arg(args, uint64_t);
#ifdef _DLIB_BIG_ENDIAN
			l = *((uint32_t *) & ll);
			l = htonl(l);
			memmove(bp, (char *)&l, sizeof(uint32_t));
			bp += sizeof (uint32_t);
			l = *(uint32_t *)((uint8_t *)&ll + sizeof(uint32_t));
			l = htonl(l);
			memmove(bp, (char *) &l, sizeof(uint32_t));
			bp += sizeof (uint32_t);
#else
			l = *(uint32_t *)((uint8_t *)&ll + sizeof(uint32_t));
			l = htonl(l);
			memmove(bp, (char *)&l, sizeof(uint32_t));
			bp += sizeof(uint32_t);
			l = *((uint32_t *)&ll);
			l = htonl(l);
			memmove(bp, (char *)&l, sizeof(uint32_t));
			bp += sizeof(uint32_t);
#endif /* endian */
			break;
#else
			l = va_arg(args, uint32_t);
			l = htonl(l);
			memmove(bp, (char *) &l, sizeof (uint32_t));
			bp += sizeof (uint32_t);
			break;
#endif /* 64 wordsize */
		case 'd':
		case 'i':
		case 'u':
			d = va_arg(args, uint32_t);
			d = htonl(d);
			memmove(bp, (char *) &d, sizeof (uint32_t));
			bp += sizeof (uint32_t);
			break;
		case 's':
			s = va_arg(args, uint32_t);
			s = htons(s);
			memmove(bp, (char *) &s, sizeof (uint16_t));
			bp += sizeof (uint16_t);
			break;
		case 'c':
			*bp++ = va_arg(args, uint32_t);
			break;
		default:
			*bp++ = *c;
			break;
		}
	}
	va_end(args);

	return (bp - buf);
}

inline int str_unpack(char *buf, char *fmt, ...)
{
	char *c;
	char *bp;

	uint64_t *ll;
	uint32_t *l;
	uint32_t *d;
	uint16_t *s;
	uint8_t *ch;

	va_list args;

	va_start(args, fmt);
	bp = buf;
	for (c = fmt; *c != '\0'; c++) {
		switch (*c) {
		case 'L':
			ll = va_arg(args, uint64_t *);
#ifdef _DLIB_BIG_ENDIAN
			l = (uint32_t *) ll;
			*l = ntohl(*(uint32_t *)bp);
			bp += sizeof(uint32_t);
			l = (uint32_t *)((uint8_t *) ll + sizeof(uint32_t));
			*l = ntohl(*(uint32_t *)bp);
			bp += sizeof(uint32_t);
#else
			l = (uint32_t *)((uint8_t *) ll + sizeof(uint32_t));
			*l = ntohl(*(uint32_t *)bp);
			bp += sizeof(uint32_t);
			l = (uint32_t *)ll;
			*l = ntohl(*(uint32_t *)bp);
			bp += sizeof(uint32_t);
#endif
			break;
		case 'l':
#if (_DLIB_WORDSIZE == 64)
			ll = va_arg(args, uint64_t *);
#ifdef _DLIB_BIG_ENDIAN
			l = (uint32_t *)ll;
			*l = ntohl(*(uint32_t *)bp);
			bp += sizeof(uint32_t);
			l = (uint32_t *)((uint8_t *)ll + sizeof(uint32_t));
			*l = ntohl(*(uint32_t *)bp);
			bp += sizeof(uint32_t);
#else
			l = (uint32_t *)((uint8_t *)ll + sizeof(uint32_t));
			*l = ntohl(*(uint32_t *)bp);
			bp += sizeof(uint32_t);
			l = (uint32_t *)ll;
			*l = ntohl(*(uint32_t *)bp);
			bp += sizeof(uint32_t);
#endif /* endian */
			break;
#else
			l = va_arg(args, uint32_t *);
			*l = ntohl(*(uint32_t *) bp);
			bp += sizeof (uint32_t);
			break;
#endif /* 64 wordsize */
		case 'd':
		case 'i':
		case 'u':
			d = va_arg(args, uint32_t *);
			*d = ntohl(*(uint32_t *) bp);
			bp += sizeof (uint32_t);
			break;
		case 's':
			s = va_arg(args, uint16_t *);
			*s = ntohs(*(uint16_t *) bp);
			bp += sizeof (uint16_t);
			break;
		case 'c':
			ch = va_arg(args, uint8_t *);
			*ch = *bp++;
			break;
		default:
			bp++;
			break;
		}
	}
	va_end(args);

	return (bp - buf);
}

inline int str_replace(char *buff, size_t buff_size,
		       const char *orig, const char *replace)
{
	if (buff == NULL || buff_size == 0 || orig == NULL)
		return -1;

	int ret = 0;

	size_t l_len = 0;
	size_t o_len = 0;
	size_t r_len = 0;

	char *p = NULL;
	char *q = NULL;
	char c_buff[buff_size];

	memset(c_buff, 0, buff_size);

	o_len = strlen(orig);
	if (replace != NULL) {
		r_len = strlen(replace);
	}

	p = q = buff;

	while (p != NULL) {
		if ((p = strstr(p, orig)) != NULL) {
			if ((o_len < r_len)
			    && ((l_len + r_len - o_len) >= buff_size)) {
				return -2;
			}

			strncat(c_buff, q, p - q);
			if (r_len != 0) {
				strncat(c_buff, replace, r_len);
			}

			l_len += p - q + r_len;

			p += o_len;
			q = p;
			ret++;
		} else {
			if ((o_len < r_len)
			    && ((l_len + strlen(q)) >= buff_size)) {
				return -2;
			}

			strcat(c_buff, q);
		}
	}

	strncpy(buff, c_buff, buff_size);

	return ret;
}

inline in_addr_t str_to_ipv4(const char *ip_str)
{
	struct in_addr ip_addr;
	inet_pton(AF_INET, ip_str, &ip_addr);
	return ip_addr.s_addr;
}

inline char *ipv4_to_str(in_addr_t ip, char *ip_str)
{
	struct in_addr ip_addr;
	if (ip_str == NULL) return NULL;

	ip_addr.s_addr = ip;
	inet_ntop(AF_INET, (const void *)&ip_addr, ip_str, INET_ADDRSTRLEN);
	return ip_str;
}

int str_to_ulong(const char *str, unsigned long *out) 
{
	char *endptr = NULL;
	unsigned long l = 0;

	if (str == NULL || out == NULL)
		return -1;

	*out = 0;
	errno = 0;

	l = strtoul(str, &endptr, 10);
	if (errno == ERANGE) {
		return 0;
	}

	if (isspace(*endptr) || (*endptr == '\0' && endptr != str)) {
		if ((long) l < 0) {
			if (strchr(str, '-') != NULL) {
			    return 0;
			}
		}
		*out = l;
		return 1;
	}

	return 0;
}

int str_to_long(const char *str, long *out) 
{
	if (str == NULL || out == NULL)
		return -1;
	errno = 0;
	*out = 0;
	char *endptr;
	long l = strtol(str, &endptr, 10);
	if (errno == ERANGE)
		return 0;
	if (isspace(*endptr) || (*endptr == '\0' && endptr != str)) {
		*out = l;
		return 1;
	}
	return 0;
}

